package com.dh.spring.client.principle;

import com.dh.spring.common.BasicConstant;
import com.dh.spring.util.BasicUtil;
import com.dh.spring.util.LocalTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.*;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;

public class NioClient {
    private static final Logger logger = LoggerFactory.getLogger(NioClient.class);
    //TODO 创建多路复用器(底层通过epoll原理实现，不限制socket文件描述符的创建，按照操作系统支持的的最大个数进行)
    private static Selector selector = null;
    private FileChannel fileChannel = null;
    //private ByteBuffer byteBuffer = null;
    private int writeBytes = 0;
    private static int fileIndex = 0;
    private ThreadLocal<Selector> threadLocal = new ThreadLocal<Selector>() {
        @Override
        protected Selector initialValue() {
            return selector;
        }
    };
    /*static {
        try {
            selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

    public static void main(String[] args) {
        NioClient nioClient = new NioClient();

        //nioServer.bootstrapNioClient();
        nioClient.multiplyFileDownload();
    }

    //TODO 多线程请求下载文件
    private void multiplyFileDownload() {
        //TODO 创建线程池
        ExecutorService executorService =
            new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors() + 2,
                Runtime.getRuntime().availableProcessors() * 2,
                1000,
                TimeUnit.MILLISECONDS,
                new SynchronousQueue<>(),
                Executors.defaultThreadFactory());
        CountDownLatch countDownLatch = new CountDownLatch(1);
        String[] fileNameArray = new String[] {"fuck.rmvb", "AA.mp4", /*"3.rar"*/}; // 待下载文件名称
        Map<String ,SocketChannelDownloadHandler> fileChannelsMap = new HashMap<>();
        Selector selector = null;

        try {
            selector = Selector.open();

            //TODO 1、封装文件信息
            StringBuffer stringBuffer = new StringBuffer("");
            for (String fileName : fileNameArray) {
                File file = new File("/Users/spring/Desktop/arthas/client/download/" + fileName);
                if(!file.exists())
                    file.createNewFile();
                SocketChannelDownloadHandler socketChannelDownloadHandler =
                    new SocketChannelDownloadHandler(
                        new RandomAccessFile(file, "rw").getChannel(), selector, countDownLatch, fileName);
                fileChannelsMap.put(fileName, socketChannelDownloadHandler);
                executorService.submit(socketChannelDownloadHandler);
            }

            countDownLatch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    class SocketChannelDownloadHandler implements Runnable {
        private FileChannel fileChannel;
        private Selector selector;
        private CountDownLatch countDownLatch;
        private String fileName;
        private int fileNameLength;
        private long filelength;
        private int isReadPackageHead = -1; //TODO 判断一次循环读取是否能够完整读取包头中包含的文件名称长度字节数据，能完整读设置其值为包头字节长度，不能则依旧为-1并且缓存可用字节数
        private int isReadFileName = -1; //TODO 是否读出文件名称
        private boolean cache = false;
        private SocketChannel socketChannel;
        private SelectionKey selectionKey = null;

        private int sendBytes = 0, writeFileLength = 0, dataPackageForFileNameLength = 4, dataPackageForFileLength = 8;
        private byte[] packageHeadForFileNameBytes = new byte[dataPackageForFileNameLength];//TODO 包头数据大小，文件名称大小四字节(int)
        private byte[] packageHeadForFileBytes = new byte[dataPackageForFileLength]; //TODO 文件长度大小八字节(long)
        private ByteBuffer sendFileBuffer = null;
        private ByteBuffer byteBuffer = ByteBuffer.allocate(2048);// TODO SocketChannel缓冲区
        private ByteBuffer cacheBuffer = ByteBuffer.allocate(1024);// TODO 缓存缓冲区
        private int readEndEOF = -1;

        public SocketChannelDownloadHandler(FileChannel fileChannel, Selector selector, CountDownLatch countDownLatch, String fileName) {
            this.fileChannel = fileChannel;
            this.selector = selector;
            this.countDownLatch = countDownLatch;
            this.fileName = fileName;
        }

        @Override
        public void run() {
            try {
                if (this.selector.isOpen()) {
                    if(!this.getSocketChannelWithConnected()) {
                        logger.info("SocketChannelDownloadHandler Error: Socket connect failed, Thread task end......");
                        return;
                    }

                    //TODO 3、发送包数据 (文件名称大小byte数组 + 文件名称字符串二进制数据)
                    sendFileBuffer.flip();
                    logger.info("已发送字节数: sendBytes = " + (sendBytes += socketChannel.write(sendFileBuffer)));

                    while(true) {
                        if(this.selector.select() == 0) { continue; }

                        Iterator iterator = selector.selectedKeys().iterator();
                        while (iterator.hasNext()) {
                            selectionKey = (SelectionKey) iterator.next();
                            iterator.remove();

                            if(selectionKey.isValid() && selectionKey.isReadable()) {
                                socketChannel = (SocketChannel) selectionKey.channel();

                                //TODO 设置缓存缓冲区
                                this.setCacheBuffer();

                                /**
                                 * TODO 如果缓存缓冲区CacheBuffer有数据了，会在SocketChannel读取到ByteBuffer前，先将缓存缓冲去数据读入ByteBuffer，然后再从SocketChannel中读取
                                 * TODO 注意此处读取为追加读取,从SocketChannel中读取数据到byteBuffer中不会覆盖缓存缓冲区提前读取进ByteBuffer中的数据
                                 * 关于SocketChannel.read(byteBuffer)返回结果的情况分析:
                                 *  = 0: 通道内没有数据可以读取，如果对端SocketChannel没有关闭输出流且没有关闭SocketChannel，则SocketChannel将死循环返回0
                                 *      此时可通过本端SocketChannel进行处理，也可以通过对端SocketChannel处理，如果发送完数据，对端应该尝试关闭输出流和关闭SocketChannel
                                 *
                                 *       (1)、如果对端关闭输出流(shutdownoutputstream)，则SocketChannel.read()会返回-1，能够跳出该SocketChannel循环，但是多路复用器
                                 *          的select()方法将无法退出，即selector.select()方法将永远返回0，等待就绪事件,进入select()方法调用的死循环
                                 *
                                 *       (2)、如果对端关闭连接(close())， 则SocketChannel.read()会返回-1，能够跳出该SocketChannel循环，但是多路复用器的select()方法将
                                 *          永远返回1，即有就绪事件，且就绪事件是上次就绪事件的再次选中，尝试从该就绪事件继续读取时，socketChannel.read()将一直返回-1，用于
                                 *          没有数据可读，读取的都是数据结尾-1的数据，这将会导致select()方法和socketChannel.read()两个方法都在无限期死循环阻塞
                                 * */
                                while ((readEndEOF = socketChannel.read(byteBuffer)) != -1) {
                                    byteBuffer.flip();

                                    if(byteBuffer.hasRemaining()) {
                                        int availableBytes = byteBuffer.remaining();
                                        if(isReadPackageHead == -1) {
                                            if(availableBytes >= (dataPackageForFileNameLength + dataPackageForFileLength)) {
                                                byteBuffer.get(packageHeadForFileNameBytes);//TODO 读取文件名称长度字节数据，转为int即为文件名称int长度
                                                byteBuffer.get(packageHeadForFileBytes);//TODO 读取文件流长度数据，转为long即为文件总大小
                                                //TODO 表示包头文件读取成功, 并获取包头文件中的数据长度(表示的是文件名称长度) ***********
                                                fileNameLength = BasicUtil.byteArrayToInt(packageHeadForFileNameBytes);
                                                filelength = BasicUtil.bytesToLong(packageHeadForFileBytes);
                                                isReadPackageHead = dataPackageForFileNameLength + dataPackageForFileLength;

                                                //TODO 开始读取文件名称
                                                if(0 < fileNameLength) {
                                                    if((availableBytes - dataPackageForFileNameLength - dataPackageForFileLength) >= fileNameLength) {
                                                        this.readFileName(availableBytes - dataPackageForFileNameLength - dataPackageForFileLength);
                                                    } else { //TODO 剩余字节长度不够文件名称数据长度，可能存在丢包，没能一次发送完整则临时记录能够可读的部分文件名称字节数据
                                                        cacheBuffer.clear();
                                                        byte[] tmpBytes = new byte[availableBytes - dataPackageForFileNameLength - dataPackageForFileLength];
                                                        byteBuffer.get(tmpBytes, 0, (availableBytes - dataPackageForFileNameLength - dataPackageForFileLength));
                                                        cacheBuffer.put(tmpBytes);
                                                        cache = true;
                                                    }
                                                } else {
                                                    logger.error("文件包头数据为空，读取失败");
                                                    break;
                                                }
                                            }  else { //TODO 读取出的可用字节不够包头大小,则缓存小于dataPackage大小的数据
                                                this.notReadPackageHeadForOneTime(availableBytes);
                                                break;
                                            }
                                        } else { //TODO 成功读取文件包头开始读取文件名称
                                            if(isReadFileName == -1) { //TODO 包头读取成功，文件名称没有读取成功，继续读取文件名称
                                                if(availableBytes > fileNameLength) { //TODO 判断读取到的可用字节数是否大于文件名称字符串长度
                                                    this.readFileName(availableBytes);
                                                } else { //TODO 不大于则再继续追加
                                                    byte[] cacheBytes = new byte[availableBytes];
                                                    byteBuffer.get(cacheBytes, 0, cacheBytes.length);byteBuffer.clear();byteBuffer.clear();
                                                    cacheBuffer.put(cacheBytes);
                                                    cache = true;
                                                }
                                            } else { //TODO 包头和文件名称都读取成功，则直接读取文件字节数据
                                                while(byteBuffer.hasRemaining()) {
                                                    logger.info("已写入字节数：writeFileLength = " + (writeFileLength += this.fileChannel.write(byteBuffer)));
                                                }
                                            }
                                        }
                                    }

                                    byteBuffer.clear();
                                }

                                if(readEndEOF == -1/* && ((long) writeFileLength) == filelength */) {
                                    selectionKey.channel();
                                    fileChannel.close();
                                    socketChannel.close();
                                    logger.info("文件下载成功......");
                                    return;
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error("SocketChannelDownloadHandler Error = " + e.getMessage());
            } finally {
            }
        }

        //TODO 连接服务端
        private boolean getSocketChannelWithConnected() throws Exception {
            boolean isConnected = false;
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            SocketDecorate.getInstance(socketChannel.socket()).setCustomKeepAlive(true).setCustomTcpNoDelay(false);
            socketChannel.register(this.selector, SelectionKey.OP_READ);

            //TODO 1、封装发送数据
            sendFileBuffer = ByteBuffer.allocate(this.fileName.getBytes().length + fileName.getBytes().length);
            sendFileBuffer.put(BasicUtil.intToBytes(this.fileName.getBytes().length));//TODO 封装包内容的大小，包内容大小int值转为字节数组后存储占4个字节
            sendFileBuffer.put(fileName.getBytes()); //TODO 封装包内容，转为byte进行存储
            logger.info("当前待下载的文件信息为：file = { fileLength = " + this.fileName.getBytes().length + ", fileName = " + fileName + " }");

            countDownLatch.await();

            //TODO 2、并发连接服务端
            socketChannel.connect(new InetSocketAddress(InetAddress.getByName("192.168.1.6"), 10087));
            while (true) { if (socketChannel.finishConnect()) { isConnected = true; break; } }

            return isConnected;
        }

        //TODO 无法成功一次性读取包头数据处理，即走缓冲缓存区存储
        private void notReadPackageHeadForOneTime(int availableBytes) {
            cacheBuffer.flip();//TODO 切换为读取模式
            //TODO 未超过CacheBuffer缓冲区容量
            if(cacheBuffer.remaining() < cacheBuffer.capacity()) {
                //TODO 切换为写模式,这里是针对CacheBuffer上此写完后的追加写,不管每次读取的可用字节数为多少都先追加
                cacheBuffer.compact();

                byte[] cacheBytes = new byte[availableBytes];
                byteBuffer.get(cacheBytes, 0, cacheBytes.length);byteBuffer.clear();
                cacheBuffer.put(cacheBytes);
                cache = true;
                cacheBuffer.flip();

                //TODO 如果缓存缓冲区中的字节数经过不断的缓存后的数据大小 大于等于 文件名称大小(4字节int)以及文件流长度(8字节long)大小
                if(cacheBuffer.remaining() >= (dataPackageForFileNameLength + dataPackageForFileLength)) {
                    cacheBuffer.get(packageHeadForFileNameBytes);//TODO 读取文件名称长度字节数据，转为int即为文件名称int长度
                    cacheBuffer.get(packageHeadForFileBytes);//TODO 读取文件流长度数据，转为long即为文件总大小
                    isReadPackageHead = dataPackageForFileNameLength + dataPackageForFileLength;
                    fileNameLength = BasicUtil.byteArrayToInt(packageHeadForFileNameBytes);
                    filelength = BasicUtil.bytesToLong(packageHeadForFileBytes);

                    if(cacheBuffer.remaining() == (dataPackageForFileNameLength + dataPackageForFileLength)) {
                        cache = false;
                        cacheBuffer.clear();
                    } else {
                        //TODO 切换为写模式，并将已经处理完的包头数据清除，请多余出来的数据移动到CacheBuffer最前头，并将position重置到已存入字节的下一个位置
                        cacheBuffer.compact();
                    }
                }
            } else {
                throw new BufferOverflowException();
            }
        }

        private void setCacheBuffer() {
            if(cache) {
                cacheBuffer.flip();
                if (cacheBuffer.hasRemaining()) { //TODO 缓存有可用数据，将缓存缓冲去数据追加到byteBuffer中

                    //TODO 先从缓存缓冲区读出数据到byte[]
                    byte[] tmpBytes = new byte[cacheBuffer.remaining()];
                    cacheBuffer.get(tmpBytes, 0, tmpBytes.length);

                    //TODO 再将数据读入byteBuffer
                    byteBuffer.put(tmpBytes);
                    cacheBuffer.compact(); //TODO 切换为写模式
                    cache = false;
                }
            }
        }

        //TODO 读取文件名称数据 dataPackage = availableBytes - dataPackageForFileNameLength - dataPackageForFileLength(已经除去文件名称和文件流大小后的可用字节)
        private void readFileName(int dataPackage) throws IOException {
            byte[] fileNameBytes = new byte[fileNameLength];
            byteBuffer.get(fileNameBytes, 0, fileNameBytes.length);
            String fileName = new String(fileNameBytes, 0, fileNameBytes.length);

            //TODO 文件名称不存在
            if(fileName.startsWith(BasicConstant.NOT_EXIST)) {
                logger.info("下载文件不存在, 文件名称: " + fileName.split(BasicConstant.MESSAGE_SPLIT)[1]);
                socketChannel.shutdownInput();
                socketChannel.close();
                return;
            }

            isReadFileName = fileNameLength;
            //TODO 存在则直接获取文件流数据
            if(dataPackage - fileNameLength > 0) { //TODO 读取完包头和包内容后还有多余的字节可读，剩余字节即为文件内容字节
                while(byteBuffer.hasRemaining()) {
                    logger.info("已写入字节数：writeFileLength = " + (writeFileLength += this.fileChannel.write(byteBuffer)));
                }
            }
        }
    }

    //TODO 多线程请求文件下载
    private void multiplyFileDownload1() {

        String result = "";
        String[] fileNameArray = new String[] {"1.rar", "2.rar", "3.rar"}; // 待下载文件名称
        int receiveBytes = 0, fileWriteBytes = 0, sendBytes = 0, fileNameLength = 0;

        Map<String ,FileChannel> fileChannelsMap = new HashMap<>();
        SocketChannel socketChannel = null;
        int isReadPackageHead = -1; //TODO 判断一次循环读取是否能够完整读取包头中包含的文件名称长度字节数据，能完整读设置其值为包头字节长度，不能则依旧为-1并且缓存可用字节数
        int isReadFileName = -1; //TODO 是否读出文件名称
        boolean cache = false;
        try {
            //TODO 1、打开通道，并启用连接
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            SocketDecorate.getInstance(socketChannel.socket()).setCustomKeepAlive(true).setCustomTcpNoDelay(false);


            //TODO 2、封装文件信息
            StringBuffer stringBuffer = new StringBuffer("");
            for(String fileName : fileNameArray) {
                stringBuffer.append(fileName + ",");
                fileChannelsMap.put(fileName, new RandomAccessFile("D:\\arthas\\client\\upload\\" + fileName, "rw").getChannel());
            }

            String tmp = stringBuffer.toString().substring(0, stringBuffer.lastIndexOf(","));
            stringBuffer.delete(0, stringBuffer.toString().length());
            stringBuffer.append(tmp + BasicConstant.MESSAGE_SPLIT + BasicConstant.SEND_RECEIVE_DATA_END);

            //TODO 3、封装发送数据包: 包结构：包大小(int值大小,即包的长度大小) + 包内容
            ByteBuffer cacheBuffer = ByteBuffer.allocate(1024);// TODO 缓存字节区
            int dataPackage = 4;
            byte[] packageHead = new byte[dataPackage];//TODO 包头数据大小，存储包内容的长度大小，长度大小用int值表示，int值在java中对应4个字节，即读取到4个字节的数据即为包的长度数据
            ByteBuffer byteBuffer = ByteBuffer.allocate(dataPackage + stringBuffer.length());

            byteBuffer.put(BasicUtil.intToBytes(stringBuffer.toString().getBytes().length));//TODO 封装包内容的大小，包内容大小int值转为字节数组后存储占4个字节
            byteBuffer.put(stringBuffer.toString().getBytes()); //TODO 封装包内容，转为byte进行存储
            byteBuffer.flip();

            //TODO 4、发送包数据
            while (byteBuffer.hasRemaining()) {
                logger.info("已发送字节数: sendBytes = " + (sendBytes += socketChannel.write(byteBuffer)));
                socketChannel.write(byteBuffer);
            }

            //TODO 5、订阅读取事件
            selector = Selector.open();
            SelectionKey selectionKey = null;
            if(selector.isOpen()) {
                socketChannel.register(selector, SelectionKey.OP_READ);
                byteBuffer = ByteBuffer.allocate(2048);

            }

            //TODO 6、订阅事件并读取数据
            while (true) {
                if(selector.select() == 0)
                    continue;

                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    selectionKey = (SelectionKey) iterator.next();

                    if(selectionKey.isValid() && selectionKey.isReadable()) {
                        socketChannel = (SocketChannel) selectionKey.channel();

                        //TODO 判断缓存缓冲区是否有缓存数据
                        if(cache) {
                            cacheBuffer.flip();
                            if (cacheBuffer.hasRemaining()) { //TODO 缓存有可用数据，将缓存缓冲去数据追加到byteBuffer中
                                byteBuffer.put(cacheBuffer);

                                /*if(-1 != isReadPackageHead) {

                                    if(-1 != isReadFileName) {

                                    }
                                    cacheBuffer.clear();
                                }*/
                                cacheBuffer.compact(); //TODO 切换为写模式
                                cache = false;
                            }
                        }

                        int length = 0;
                        while ((length = socketChannel.read(byteBuffer)) != -1) {
                            byteBuffer.flip();

                            //TODO 首四个字节：文件名称长度(int转byte[4]后的字节表示）  紧跟：文件名称占据的字节数据长度  剩余：当前文件的字节数据
                            if(byteBuffer.hasRemaining()) {
                                int availableBytes = byteBuffer.remaining();//TODO 获取当前一次循环从ByteBuffer读取到的可用数据长度

                                if(isReadPackageHead == -1) { //TODO 没有读出包头，先读出包头
                                    if(availableBytes >= dataPackage) { //TODO 判断读取出的可用字节是否大于包头数据大小

                                        byteBuffer.get(packageHead);//TODO 读取packageHead长度的字节到packageHead数组，此数据转为int为文件名称字符串的长度
                                        fileNameLength = BasicUtil.byteArrayToInt(packageHead);
                                        isReadPackageHead = fileNameLength; //TODO 表示包头文件读取成功 ***********

                                        if(0 < fileNameLength) { //TODO fileNameLength文件名称字符串长度
                                            if(isReadFileName == -1) { //TODO 包头读取成功，尝试读取文件名称，如果isReadFileName = -1：没有读出文件名称，先读取文件名称
                                                //TODO 一次循环读取到的可用字节数减去首个文件名称字节数组长度后剩余的字节数如果大于文件名称int长度，表明剩余的字节数据不仅包含了
                                                // 文件名称数据还包含了文件字节流数据，如果剩余字节数刚好等于文件名称字节长度，说明一次读取到的byte数据只包含文件名称字节长度和文件名称长度
                                                if((availableBytes - dataPackage >= fileNameLength)) {
                                                    //TODO 读取文件名称
                                                    byte[] fileNameBytes = new byte[fileNameLength];
                                                    byteBuffer.get(fileNameBytes, 0, fileNameBytes.length);
                                                    String fileName = new String(fileNameBytes, 0, fileNameBytes.length);

                                                    //TODO 文件名称读取成功，启动线程开始读取对应文件的流数据
                                                    if(fileChannelsMap.containsKey(fileName)) {
                                                    }
                                                } else { //TODO 剩余字节长度不够文件名称数据长度，可能存在丢包，没能一次发送完整则临时记录能够可读的部分文件名称字节数据
                                                    cacheBuffer.clear();
                                                    cacheBuffer.put(byteBuffer);
                                                    cache = true;
                                                }
                                            }
                                        } else {
                                            logger.error("文件包头数据为空，读取失败");
                                            break;
                                        }
                                    } else { //TODO 读取出的可用字节不够包头大小,则缓存小于dataPackage大小的数据
                                        cacheBuffer.flip();//TODO 切换为读取模式
                                        if(cacheBuffer.remaining() < cacheBuffer.capacity()) { //TODO 未超过CacheBuffer缓冲区容量
                                            //TODO 切换为写模式,这里是针对CacheBuffer上此写完后的追加写,不管每次读取的可用字节数为多少都先追加
                                            cacheBuffer.compact();
                                            cacheBuffer.put(byteBuffer);
                                            cache = true;

                                            cacheBuffer.flip();
                                            if(cacheBuffer.remaining() == dataPackage) { //TODO 如果缓存缓冲区中的字节数加上已读取的字节数刚好等于包头大小,重置并清空
                                                cacheBuffer.get(packageHead);
                                                isReadPackageHead = BasicUtil.byteArrayToInt(packageHead);
                                                cache = false;
                                                cacheBuffer.clear();
                                                break;
                                            }
                                            if(cacheBuffer.remaining() > dataPackage) { //TODO 如果缓存缓冲区中的字节数加上已读取的字节数超过包头大小,重置并将多余的移动到缓存缓冲去最前头
                                                cacheBuffer.get(packageHead);
                                                isReadPackageHead = BasicUtil.byteArrayToInt(packageHead);
                                                //TODO 切换为写模式，并将已经处理完的包头数据清除，请多余出来的数据移动到CacheBuffer最前头，并将position重置到已存入字节的下一个位置
                                                cacheBuffer.compact();
                                            }
                                        } else {
                                            throw new BufferOverflowException();
                                        }
                                    }
                                } else { //TODO 成功读取文件包头
                                    if(isReadFileName == -1) { //TODO 包头读取成功，文件名称没有读取成功，继续读取文件名称

                                    } else { //TODO 包头和文件名称都读取成功，则直接读取文件字节数据

                                    }
                                }

                            }
                        }
                    }
                }
            }

            //TODO 发送待下载文件信息
            /*String data = fileName[fileIndex - 1] + BasicConstant.MESSAGE_SPLIT + BasicConstant.SEND_RECEIVE_DATA_END;
            socketChannel.write(ByteBuffer.wrap((data).getBytes(), 0, data.length()));

            //TODO 读取下载文件反馈信息(是否存在，能否下载等)
            StringBuffer stringBuffer = new StringBuffer("");
            while ((receiveBytes = socketChannel.read(byteBuffer)) != -1) {
                stringBuffer.append(new String(byteBuffer.array(), 0, receiveBytes));
                System.out.println(stringBuffer.toString());
                if (stringBuffer.toString().endsWith(BasicConstant.SEND_RECEIVE_DATA_END)) {
                    receiveBytes = 0;
                    break;
                }
            }
            if(stringBuffer.toString().contains(BasicConstant.MESSAGE_SPLIT)) {
                String status = stringBuffer.toString().split(BasicConstant.MESSAGE_SPLIT)[0];
                String message = stringBuffer.toString().split(BasicConstant.MESSAGE_SPLIT)[1];
                if (status.contains("FALSE")) {
                    return message;
                }

                //TODO 读取文件

                byteBuffer.clear();
                fileChannel = new RandomAccessFile("D:\\arthas\\client\\upload\\" + fileName[fileIndex - 1], "rw").getChannel();
                while ((receiveBytes = socketChannel.read(byteBuffer)) != -1) {
                    byteBuffer.flip();
                    logger.info("已接收字节数: receiveBytes = " + receiveBytes + ", 已写入字节数 ：fileWriteBytes = " + (fileWriteBytes += socketChannel.write(byteBuffer)));
                    byteBuffer.compact();
                }
            } else {
                socketChannel.shutdownInput();
                socketChannel.shutdownOutput();
                return "下载失败";
            }*/

        } catch (Exception e) {
            logger.error("download file 异常，堆栈信息：{} ", e);
        } finally {
            if (receiveBytes == -1) {
                if(null != fileChannel) {
                    try {
                        logger.info("文件写入成功,总共写入大小 writeBytes = " + writeBytes + " 关闭文件通道");
                        writeBytes = 0;
                        fileChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if(null != socketChannel) {
                    try {
                        socketChannel.shutdownInput();
                        logger.info("关闭SocketChannel通道");
                        //socketChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void bootstrapNioClient() {
        SocketChannel socketChannel = null;
        try {
            selector = Selector.open();

            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            SocketDecorate.getInstance(socketChannel.socket()).setCustomKeepAlive(true).setCustomTcpNoDelay(false);
            socketChannel.connect(new InetSocketAddress(InetAddress.getByName("10.33.75.229"), 10089));

            if(this.selector.isOpen()) {
                //TODO I/O多路复用技术将传统的Socket BIO技术中对于Socket的阻塞转为多路复用器中针对Selector的阻塞，只是阻塞对象发生
                // 了变化
                while(true) {
                    /**
                     * 正常情况下select()方法以阻塞方式运行，即注册在Selector对象上的所有通道没有发生任何就绪事件时select()方法会一直阻塞
                     * 直到有就绪事件的发生，一旦有任意数量的通道上发生就绪事件，select()方法就会返回大于0，表明监听的通道集合中有就绪事件发生
                     *  1、select() == 0
                     *      首先select()方法一直返回0，如果没指明select()方法按照其阻塞多长时间进行调用的话，select()方法还一直返回0说明
                     *    不是由于超时引起，但是监听的通道所发生的就绪事件返回却是0，能够返回，按理说如果监听的通道没有就绪事件发生的话select()
                     *    方法应该是一直阻塞，所以，这种现象可能的原因为服务端造成，服务端并没有关闭通道的输出或输入，导致通道一直开启状态
                     * select()方法思循环返回0分析：
                     *
                     * */
                    if(this.selector.select() == 0) {
                        continue;
                    }

                    Iterator iterator = this.selector.selectedKeys().iterator();
                    while(iterator.hasNext()) {
                        SelectionKey selectionKey = (SelectionKey) iterator.next();
                        if(selectionKey.isValid()) {
                            if(selectionKey.isConnectable()) {
                                /**
                                 * TODO 连接请求已发起，循环等待与服务端socket三次握手成功建立，每应答一次，都在序号基础上加1进行应答，
                                 *  客户端应答服务端则将服务端发送的序号加1进行回应 服务端应答客户端则将客户端发送的序号加1进行回应，即
                                 *  每次应答都是执行加1操作进行，四次挥手断开连接同样，所以不论是客户端还是服务端只在应答时序号完成加1
                                 *  操作并且成功发送对端，对端真正接受到才算一次请求成功；三次握手如下
                                 *      如例：客户端请求服务端：
                                 *                                      SYN    Seq=LSN(c)
                                 *          Client ---------------------------------------------------------->  Server
                                 *                          SYN     Seq=LSN(s)   ACK=LSN(c)+1
                                 *          client <----------------------------------------------------------  Server
                                 *                          ACK     ACK=LSN(s)+1
                                 *          Client <----------------------------------------------------------  Server
                                 *      服务端发送序号[ACK=LSN(c)+1]对客户端连接请求应答，并发送Seq=LSN(s)用于客户端同意服务端的请求
                                 *      后并在此序号Seq=LSN(s)上加1进行对服务端请求的应答，即客户端应答数据必须为服务端序号LSN(s)+1
                                 * */
                                while (true) {if(socketChannel.finishConnect()) {break;}}
                                logger.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] ----------- 与服务器连接成功,并未当前通道注册读写事件后向服务端发响应数据-----------");
                                //TODO 连接完成后通过对当前SelectionKey注册OP_READ读事件和OP_WRITE写事件执行数据发送,将要发送的数据封装进当前SelectionKey中的Attachment对象中
                                selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_READ | SelectionKey.OP_WRITE);

                                String ack = "NioClient客户端 [ " + socketChannel.getLocalAddress() + " ] 同意服务端 [ " + socketChannel.getRemoteAddress() + " ] 的连接请求";
                                selectionKey.attach(ByteBuffer.wrap(ack.getBytes()));
                            } else if(selectionKey.isReadable()) {
                                readChannelHandler(selectionKey);
                            } else if(selectionKey.isWritable()) {
                                writeChannelHandler(selectionKey);
                            }
                        }
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            if(e instanceof ConnectException) {
                logger.error("NioClient Connection error = " + e.getMessage());
            }
        }
    }

    //TODO 执行读取事件
    private void readChannelHandler(SelectionKey selectionKey){
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        int length = 0;
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.clear();
        try {
            //TODO 读取数据
            if(null == fileChannel) {
                fileChannel = new RandomAccessFile("D:\\arthas\\server\\download\\" + String.valueOf(selectionKey.attachment()) + ".rar", "rw").getChannel(); //297575
            }

            if(null != fileChannel && fileChannel.isOpen()) {

                while ((length = socketChannel.read(byteBuffer)) > 0) {
                    byteBuffer.flip();
                    while(byteBuffer.hasRemaining()) {
                        logger.info("已接收字节数：writeByte = " + (writeBytes += fileChannel.write(byteBuffer)));
                    }
                    byteBuffer.compact();
                }
            }
        } catch (IOException e) {
            logger.error("BootStrapAcceptRunnable [readChannelHandler] error = " + e.getMessage());
        } finally {
            if (length == -1) {
                if(null != fileChannel) {
                    try {
                        logger.info("文件写入成功,总共写入大小 writeBytes = " + writeBytes + " 关闭文件通道");
                        writeBytes = 0;
                        fileChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if(null != socketChannel) {
                    try {
                        socketChannel.shutdownInput();
                        logger.info("关闭SocketChannel通道");
                        selectionKey.cancel();
                        //socketChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            logger.info("当前select()批次读取后总共写入大小 writeBytes = " + writeBytes);
        }
    }

    //TODO 执行写事件
    private void writeChannelHandler(SelectionKey selectionKey){
        try {
            //TODO 获取当前选择键需要发送的数据对象
            ByteBuffer byteBuffer = ByteBuffer.wrap(String.valueOf(selectionKey.attachment()).split(",")[1].getBytes());
            /**
             * 如果当前客户端SocketChannel发送缓冲区一直过满导致应用数据一直无法发送到缓冲区，此时byteBuffer中一直存在数据，
             * 会导致while死循环，且write()方法会一直返回为0，表示一直写不进发送缓冲区，此时有可能为客户端网络不稳定或是网络
             * 状态不好，导致内核发送缓冲区的数据发送出去过慢，短时间内发送缓冲区一直过满，write()方法一直写入不成功
             * */
            int failedSendCount = 0;
            while(byteBuffer.hasRemaining()) {
                //logger.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] ----------- 发送数据：data = { " + new String(byteBuffer.array()) + " } -----------");
                int sentBytes = ((SocketChannel) selectionKey.channel()).write(byteBuffer);
                //logger.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] ----------- write已发送数据大小：size = { " + sentBytes + " 字节} -----------");

                if(0 == sentBytes) {
                    if(failedSendCount == 5) { //TODO 当累积write调用成功写入的字节数为0的次数达到failedSendCount次数(5)将终止该循环，取消当前可选键写事件的处理
                        //TODO 取消注册的写事件
                        selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
                        selectionKey.selector().selectNow();
                        break;
                    }

                    failedSendCount++;
                }
            }

            if(failedSendCount == 5) {
                //TODO 重新注册写事件
                selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
            }

            //TODo 如果网络正常，且write方法发送数据成功，则取消当前可选键的写事件注册终止选择键的写事件触发
            selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
        } catch (IOException e) {
            logger.error("writeChannelHandler error = " + e.getMessage());
            selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
        }
    }

    static class AttachmentObject {
        private ByteBuffer byteBuffer;
        public static AttachmentObject getInstance() {
            return null;
        }
    }

    //TODO Socket装饰类
    static class SocketDecorate extends Socket {
        private static Socket _socket;
        public SocketDecorate() {}
        public static SocketDecorate getInstance(Socket socket) {
            _socket = socket;
            return new SocketDecorate();
        }

        private Socket getSocket(){
            return _socket;
        }

        public SocketDecorate setCustomTcpNoDelay(boolean on) throws SocketException {
            _socket.setTcpNoDelay(on);
            return this;
        }

        public SocketDecorate setCustomKeepAlive(boolean on) throws SocketException {
            _socket.setKeepAlive(on);
            return this;
        }

        public SocketDecorate setCustomSoLinger(boolean on, int linger) throws SocketException {
            _socket.setSoLinger(on, linger);
            return this;
        }
    }

    //TODO 读数据处理
    static class ReadChannelHandler implements Runnable {
        private SocketChannel socketChannel;
        public ReadChannelHandler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            FileChannel fileChannel = null;
            try {
                //TODO 读取数据
                ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
                fileChannel = new RandomAccessFile("D:\\arthas\\server\\download\\123.tar", "rw").getChannel();
                int length = 0;
                while (socketChannel.read(byteBuffer) > 0) {
                    byteBuffer.flip();
                    while(byteBuffer.hasRemaining()) {
                        length = fileChannel.write(byteBuffer);
                        logger.info("已发送字节数：bytes = " + length);
                    }
                    byteBuffer.clear();
                }
            } catch (IOException e) {
                logger.error("BootStrapAcceptRunnable [readChannelHandler] error = " + e.getMessage());
            } finally {
                /*if(null != fileChannel) {
                    try {
                        fileChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if(null != this.socketChannel) {
                    try {
                        this.socketChannel.shutdownInput();
                        //this.socketChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }*/
            }
        }
    }



    /*static class BootStrapClientRunnable implements Runnable {
        private Selector selector;
        public BootStrapClientRunnable(Selector selector) {
            this.selector = selector;
        }

        @Override
        public void run() {

        }


    }*/
}
